package net.tavion.angelboot.framework.logger.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import net.tavion.angelboot.framework.logger.MDCUtil;
import net.tavion.angelboot.framework.logger.rr.HttpLogNode;
import net.tavion.angelboot.framework.logger.rr.WebfluxServerRequestDecorator;
import net.tavion.angelboot.framework.logger.rr.WebfluxServerResponseDecorator;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.UUID;


/**
 * 通用日志记录，主要用于接口的请求与响应报文
 * 另外一个可行解决方案见
 * https://my.oschina.net/junjunyuanyuankeke/blog/2253493
 */
@Slf4j
@Component
public class GenericLoggerFilter implements WebFilter , Ordered {

    @Override
    public int getOrder() {
        return -3;
    }

    @Resource
    ObjectMapper objectMapper;

//    @Override
//    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
//        //后处理，打印完成信号的值
//        return chain.filter(exchange).doFinally(signal ->  System.out.println("signal="+signal.toString()));
//
//        /* 其他几种
//         * or 1: 建议尽量采用链式的fluent连贯写法
//         *   Mono<Void>  completeMono = chain.filter(exchange);
//         *   return completeMono.doFinally(signal -> System.out.println("signal="+signal.toString()));
//         */
//        //or 2: return chain.filter(exchange).thenEmpty(other);
//        //or 3: return chain.filter(exchange).thenMany(other).map(..)....then();
//    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {

        LocalDateTime requestTime=LocalDateTime.now();
        String requestScheme=exchange.getRequest().getURI().getScheme();

        if(!requestScheme.equalsIgnoreCase("http") && !requestScheme.equalsIgnoreCase("https") ){
            return chain.filter(exchange);
        }

        String hostIp=exchange.getRequest().getHeaders().getHost().getHostString();
        String httpMethod=exchange.getRequest().getMethod().toString();
        String requestUri=exchange.getRequest().getURI().toString();

        //1、获取头部的请求id作为日志号
        String requestId=exchange.getRequest().getHeaders().getFirst("x-requestId");
        if(StringUtils.isBlank(requestId)){
            requestId=UUID.randomUUID().toString().replaceAll("-","");
            log.debug("生成requestId,{}",requestId);
        }

        MDCUtil.putMDCKey(requestId);

        ServerHttpRequest addRequest = exchange.getRequest().mutate().header("x-requestId", requestId).build();
        WebfluxServerRequestDecorator requestDecorator=new WebfluxServerRequestDecorator(addRequest);

        HttpLogNode responseLogNode=new HttpLogNode();
        WebfluxServerResponseDecorator responseDecorator = new WebfluxServerResponseDecorator(exchange.getResponse(), responseLogNode);

        ServerWebExchange ex = exchange.mutate()
                .request(requestDecorator)
                .response(responseDecorator)
                .build();

        HttpLogNode requestLogNode=new HttpLogNode();
        responseDecorator.beforeCommit(() -> Mono.defer(() -> loggerResponse(requestTime,requestUri,responseLogNode,responseDecorator)));
        return loggerAndChain( ex, requestLogNode,requestUri,hostIp,httpMethod)
                .then(chain.filter(ex).doFinally(
                        rsp->{
                            log.info("{}",rsp.toString());
                            MDCUtil.removeMDCKey();
                        }
                )).then();
    }

    private Mono<Void> loggerResponse(LocalDateTime requestTime,String uri, HttpLogNode logNote, ServerHttpResponse response) {

        long duration = Duration.between(requestTime,LocalDateTime.now()).toMillis();

        HttpStatus statusCode = response.getStatusCode();
        assert statusCode != null;

        String httpStatus=statusCode.value()+" "+statusCode.getReasonPhrase();
        HttpHeaders headers = response.getHeaders();
        logNote.getHeaders().putAll(headers);

        if(log.isDebugEnabled()){
            String headerStr=writeAsString(logNote.getHeaders(),objectMapper);
            log.debug("{} 请求处理完成,耗时{}毫秒,状态:{} --> [响应头数据:{}]",uri,duration,httpStatus,headerStr);
        }

        if(response.getStatusCode().is2xxSuccessful() || logNote.getBody()!=null){
            log.info("{} 请求处理完成,耗时{}毫秒,状态:{} --> [响应数据:{}]",uri,duration,httpStatus,logNote.getBody());
        }


        return Mono.empty();
    }



    private Mono<Void> loggerAndChain(ServerWebExchange exchange, HttpLogNode logNode,String uri,String hostIp,String httpMethod) {
        HttpHeaders headers = exchange.getRequest().getHeaders();
        logNode.getHeaders().putAll(headers);
        if(log.isDebugEnabled()){
            String headerStr=writeAsString(logNode.getHeaders(),objectMapper);
            log.debug("{}发起{}请求{} --> [请求头数据:{}]",hostIp,httpMethod,uri,headerStr);
        }


        Charset bodyCharset = null;
        if(headers.getContentLength() >0 && hasRequestBody(headers.getContentType())){
            bodyCharset = getMediaTypeCharset(headers.getContentType());
            return readAndLogRequest(exchange.getRequest().getBody(), bodyCharset,hostIp,uri,httpMethod, logNode).then(Mono.defer(() -> Mono.empty()));
        }
        return Mono.empty();
    }

    private Mono<Void> readAndLogRequest(Flux<DataBuffer> body, Charset charset,String hostIp,String uri,String httpMethod, HttpLogNode logNotes) {
        return DataBufferUtils.join(body).doOnNext(buffer -> {
            CharBuffer charBuffer = charset.decode(buffer.asByteBuffer());
            //记录我实体的请求体
            logNotes.setBody(charBuffer.toString());

            log.info("{}发起{}请求{} --> [请求数据:{}]",hostIp,httpMethod,uri,charBuffer.toString());
            DataBufferUtils.release(buffer);
        }).then();
    }


    private boolean hasRequestBody(MediaType contentType) {
        if(contentType==null){
            return false;
        }
        String type = contentType.getType();
        String subType = contentType.getSubtype();

        if ("application".equals(type)) {
            return "json".equals(subType) || "x-www-form-urlencoded".equals(subType) || "xml".equals(subType) || "atom+xml".equals(subType) || "rss+xml".equals(subType);
        } else if ("text".equals(type)) {
            return true;
        }
        //暂时不记录form
        return false;
    }

    private Charset getMediaTypeCharset(@Nullable MediaType mediaType) {
        if (mediaType != null && mediaType.getCharset() != null) {
            return mediaType.getCharset();
        } else {
            return StandardCharsets.UTF_8;
        }
    }

    private <T> String writeAsString(T object, ObjectMapper objectMapper) {
        try {
            return objectMapper.writeValueAsString(object);
        } catch (Exception var3) {
            log.error(var3.getMessage(),var3);
            return null;
        }
    }

}